﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;

namespace Xunit.Sdk
{
	/// <summary>
	/// The test runner for xUnit.net v2 tests.
	/// </summary>
	public class XunitTestRunner : TestRunner<IXunitTestCase>
	{
#if NET_4_0_ABOVE
		private readonly IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes;
#else
		private readonly IList<BeforeAfterTestAttribute> beforeAfterAttributes;
#endif

		/// <summary>
		/// Initializes a new instance of the <see cref="XunitTestRunner"/> class.
		/// </summary>
		/// <param name="test">The test that this invocation belongs to.</param>
		/// <param name="messageBus">The message bus to report run status to.</param>
		/// <param name="testClass">The test class that the test method belongs to.</param>
		/// <param name="constructorArguments">The arguments to be passed to the test class constructor.</param>
		/// <param name="testMethod">The test method that will be invoked.</param>
		/// <param name="testMethodArguments">The arguments to be passed to the test method.</param>
		/// <param name="skipReason">The skip reason, if the test is to be skipped.</param>
		/// <param name="beforeAfterAttributes">The list of <see cref="BeforeAfterTestAttribute"/>s for this test.</param>
		/// <param name="aggregator">The exception aggregator used to run code and collect exceptions.</param>
		/// <param name="cancellationTokenSource">The task cancellation token source, used to cancel the test run.</param>
		public XunitTestRunner(ITest test,
													 IMessageBus messageBus,
													 Type testClass,
													 object[] constructorArguments,
													 MethodInfo testMethod,
													 object[] testMethodArguments,
													 string skipReason,
#if NET_4_0_ABOVE
 IReadOnlyList<BeforeAfterTestAttribute> beforeAfterAttributes,
#else
 IList<BeforeAfterTestAttribute> beforeAfterAttributes,
#endif
 ExceptionAggregator aggregator,
													 CancellationTokenSource cancellationTokenSource)
			: base(test, messageBus, testClass, constructorArguments, testMethod, testMethodArguments, skipReason, aggregator, cancellationTokenSource)
		{
			this.beforeAfterAttributes = beforeAfterAttributes;
		}

		/// <summary>
		/// Gets the list of <see cref="BeforeAfterTestAttribute"/>s for this test.
		/// </summary>
#if NET_4_0_ABOVE

		protected IReadOnlyList<BeforeAfterTestAttribute> BeforeAfterAttributes
#else

		protected IList<BeforeAfterTestAttribute> BeforeAfterAttributes
#endif
		{
			get { return beforeAfterAttributes; }
		}

		/// <inheritdoc/>
		protected override async Task<Tuple<decimal, string>> InvokeTestAsync(ExceptionAggregator aggregator)
		{
			var output = String.Empty;
			var testOutputHelper = ConstructorArguments.OfType<TestOutputHelper>().FirstOrDefault();
			if (testOutputHelper != null)
				testOutputHelper.Initialize(MessageBus, Test);

			var executionTime = await InvokeTestMethodAsync(aggregator);

			if (testOutputHelper != null)
			{
				output = testOutputHelper.Output;
				testOutputHelper.Uninitialize();
			}

			return Tuple.Create(executionTime, output);
		}

		/// <summary>
		/// Override this method to invoke the test method.
		/// </summary>
		/// <param name="aggregator">The exception aggregator used to run code and collect exceptions.</param>
		/// <returns>Returns the execution time (in seconds) spent running the test method.</returns>
		protected virtual async Task<decimal> InvokeTestMethodAsync(ExceptionAggregator aggregator)
		{
			return await new XunitTestInvoker(Test, MessageBus, TestClass, ConstructorArguments, TestMethod, TestMethodArguments, BeforeAfterAttributes, aggregator, CancellationTokenSource).RunAsync();
		}
	}
}